/*
 * ArrayType.java
 *
 * Created on July 31, 2007, 8:06 PM
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.variable.types;

import java.util.ArrayList;
import java.util.List;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.matchers.ValueMatcher;
import tal.drivechain.wildcard.Wildcard;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.expression.dcExpression;
import tal.drivechain.variable.Variable;
import tal.drivechain.wildcard.DoubleElement;
import tal.drivechain.variable.expressions.CustomExpression;

/**
 * A DataType class that handles arrays of other values of a specific data type.
 * Name: "typename[]"
 * Value object: Array
 * Type code: 100 + the element data type code (104 for an array of ints).
 * Syntax: <element type>[]:<element's type values>,...,<value>
 * Properties: varname#length returns the number of elements contained in the array Variable.
 * Can cast from any datatype by creating a 1 element array containing the cast value.
 *
 * @author Tal Eisenberg
 */
public final class ArrayType extends DataType implements IndexableType {
    DataType elementType;
    
    /** Creates a new instance of ArrayType */
    public ArrayType(DataType elementType) {
        super(elementType.getName() + "[]", ArrayList.class, 100 + elementType.getTypeCode());
        this.elementType = elementType;
        if (elementType.matches(DataType.EXPR))
            throw new IllegalArgumentException("Can't create an array of expr variables.");
    }

    public Object parseValue(String value) throws IllegalArgumentException {
        if (value.length()!=0 && value.charAt(0)=='(' && (Tokenizer.balanced(value, 0, '(', ')')==value.length()-1))
                value = value.substring(1, value.length()-1);
        Tokenizer parser = new Tokenizer(value, ',');
        List<Object> arrayobj = new ArrayList<Object>();
        while (parser.hasMoreTokens()) {
            String token = parser.nextToken().trim();
            if (token.length()!=0) {
                token = token.trim();
                arrayobj.add(elementType.parseValue(token));
            }
        }
        return arrayobj;
    }

    @Override
    public Object cast(Object value) throws IllegalArgumentException {
        try {
            return super.cast(value);
        } catch (IllegalArgumentException ie) {
            Object casted = elementType.cast(value);
            List ret = new ArrayList();
            ret.add(casted);
            return ret;
        }
    }

    public DataType getElementType() { return elementType; }
    
    @Override
    public String toString(Object value, ChainNode context) {
        if (value!=null) {
            String out = "";
            List<Object> val = (List<Object>)value;
            for (int i=0; i<val.size(); i++) 
                out += elementType.toString(val.get(i), context) + ", ";
            if (out.length()>0)
                out = out.substring(0, out.length()-2);
            return out;
                
        } else return "null-value";
    }

    @Override
    public CustomExpression createExpression(String value) throws IllegalArgumentException {
        throw new IllegalArgumentException("An expression can't be assigned to an array.");
    }

    @Override
    public ValueMatcher createValueMatcher() throws IllegalArgumentException {
        throw new IllegalArgumentException("Value matchers are not supported for array variables.");
    }

    @Override
    public Variable evaluateVar(Variable var, ChainNode context) {
        ArrayList<Object> elements = (ArrayList<Object>)var.getInternalValue();
        List<Object> dest = (List<Object>)elements.clone();
        int size = elements.size();
        for (int i=0; i<size; i++) {
            Object val = elements.get(i);
            Variable tmpVar = new Variable("temp", elementType, val, true);
            dest.set(i, elementType.cast(elementType.evaluateVar(tmpVar, context).getInternalValue()));
        }
        return new Variable(var.getName(), new ArrayType(elementType), dest, true);
    }
    
    public Object setByIndex(Object workon, Variable value, dcExpression index, ChainNode context) throws IllegalArgumentException {
        if (!(index.getReturnVarType().matches(DataType.INT)))
            throw new IllegalArgumentException("Can't get substring from a keyword index: " + index);
        
        List<Object> array = (List<Object>)workon;
        int idx = ((Integer)index.evaluate(context)).intValue();
        if (idx<0 || idx>=array.size()) 
            throw new IllegalArgumentException("Index out of range: " + idx);
        
        //check type, if wrong try to cast.
        Object valToSet;
        if (!value.getDataType().matches(elementType)) {
            valToSet = elementType.cast(value.getInternalValue());
        } else valToSet = value.getInternalValue();
        
        //put value in the array list
        array.add(idx, valToSet);
        return array;
    }

    public Variable getByIndex(Wildcard index, Variable from, ChainNode context) throws IllegalArgumentException {
        if (!(index.getWildcardElement() instanceof DoubleElement))
            throw new IllegalArgumentException("Can't get substring from a keyword index: " + index);
        List<Object> array = (List<Object>)from.getInternalValue();
        List<Object> retarray = new ArrayList<Object>();
        String nameEnd = "";
        for (int i=0; i<array.size(); i++)
            if (index.matches(i, context)) { retarray.add(array.get(i)); nameEnd += "" + i; }
        if (nameEnd.length()>8) nameEnd=nameEnd.substring(0, 5) + "$etc";
        if (retarray.isEmpty())
            return new Variable(from.getName() + "$empty", this, new ArrayList<Object>(), true);
        else if (retarray.size()==1)
            return new Variable(from.getName() + "$" + nameEnd, this.getElementType(), retarray.get(0), true);
        else return new Variable(from.getName() + "$" + nameEnd, this, retarray, true);
    }

    public boolean isNestingAllowed() {
        return true;
    }

    public Object getNextDimension(Object workon, Object modifier, ChainNode context) {
        int idx = (Integer)((dcExpression)modifier).evaluate(context);
        return ((List)workon).get(idx);            
    }
}
